/*
 * Copyright 2025 New Vector Ltd.
 *
 * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
 * Please see LICENSE files in the repository root for full details.
 */

import { type GeneratedSecretStorageKey } from "matrix-js-sdk/src/crypto-api";

import { test, expect } from ".";
import {
    checkDeviceIsConnectedKeyBackup,
    checkDeviceIsCrossSigned,
    createBot,
    deleteCachedSecrets,
    verifySession,
} from "../../crypto/utils";

test.describe("Encryption tab", () => {
    test.use({ displayName: "Alice" });

    test.describe("when encryption is set up", () => {
        let recoveryKey: GeneratedSecretStorageKey;
        let expectedBackupVersion: string;

        test.beforeEach(async ({ page, homeserver, credentials }) => {
            // The bot bootstraps cross-signing, creates a key backup and sets up a recovery key
            const res = await createBot(page, homeserver, credentials);
            recoveryKey = res.recoveryKey;
            expectedBackupVersion = res.expectedBackupVersion;
        });

        test(
            "should show a 'Verify this device' button if the device is unverified",
            { tag: "@screenshot" },
            async ({ page, app, util }) => {
                const dialog = await util.openEncryptionTab();
                const content = util.getEncryptionTabContent();

                // The user's device is in an unverified state, therefore the only option available to them here is to verify it
                const verifyButton = dialog.getByRole("button", { name: "Verify this device" });
                await expect(verifyButton).toBeVisible();
                await expect(content).toMatchScreenshot("verify-device-encryption-tab.png");
                await verifyButton.click();

                await util.verifyDevice(recoveryKey);

                await expect(content).toMatchScreenshot("default-tab.png", {
                    mask: [content.getByTestId("deviceId"), content.getByTestId("sessionKey")],
                });

                // Check that our device is now cross-signed
                await checkDeviceIsCrossSigned(app);

                // Check that the current device is connected to key backup
                // The backup decryption key should be in cache also, as we got it directly from the 4S
                await checkDeviceIsConnectedKeyBackup(app, expectedBackupVersion, true);
            },
        );

        // Test what happens if the cross-signing secrets are in secret storage but are not cached in the local DB.
        //
        // This can happen if we verified another device and secret-gossiping failed, or the other device itself lacked the secrets.
        // We simulate this case by deleting the cached secrets in the indexedDB.
        test(
            "should prompt to enter the recovery key when the secrets are not cached locally",
            { tag: "@screenshot" },
            async ({ page, app, util }) => {
                await verifySession(app, recoveryKey.encodedPrivateKey);
                // We need to delete the cached secrets
                await deleteCachedSecrets(page);

                await util.openEncryptionTab();
                // We ask the user to enter the recovery key
                const dialog = util.getEncryptionTabContent();
                const enterKeyButton = dialog.getByRole("button", { name: "Enter recovery key" });
                await expect(enterKeyButton).toBeVisible();
                await expect(dialog).toMatchScreenshot("out-of-sync-recovery.png");
                await enterKeyButton.click();

                // Fill the recovery key
                await util.enterRecoveryKey(recoveryKey);
                await expect(dialog).toMatchScreenshot("default-tab.png", {
                    mask: [dialog.getByTestId("deviceId"), dialog.getByTestId("sessionKey")],
                });

                // Check that our device is now cross-signed
                await checkDeviceIsCrossSigned(app);

                // Check that the current device is connected to key backup
                // The backup decryption key should be in cache also, as we got it directly from the 4S
                await checkDeviceIsConnectedKeyBackup(app, expectedBackupVersion, true);
            },
        );

        test("should display the reset identity panel when the user clicks on 'Forgot recovery key?'", async ({
            page,
            app,
            util,
        }) => {
            await verifySession(app, recoveryKey.encodedPrivateKey);
            // We need to delete the cached secrets
            await deleteCachedSecrets(page);

            // The "Key storage is out sync" section is displayed and the user click on the "Forgot recovery key?" button
            await util.openEncryptionTab();
            const dialog = util.getEncryptionTabContent();
            await dialog.getByRole("button", { name: "Forgot recovery key?" }).click();

            // The user is prompted to reset their identity
            await expect(
                dialog.getByText("Forgot your recovery key? You’ll need to reset your identity."),
            ).toBeVisible();
        });

        test("should warn before turning off key storage", { tag: "@screenshot" }, async ({ page, app, util }) => {
            await verifySession(app, recoveryKey.encodedPrivateKey);
            await util.openEncryptionTab();

            await page.getByRole("switch", { name: "Allow key storage" }).click();

            await expect(
                page.getByRole("heading", { name: "Are you sure you want to turn off key storage and delete it?" }),
            ).toBeVisible();

            await expect(util.getEncryptionTabContent()).toMatchScreenshot("delete-key-storage-confirm.png");

            const deleteRequestPromises = [
                page.waitForRequest((req) => req.url().endsWith("/account_data/m.cross_signing.master")),
                page.waitForRequest((req) => req.url().endsWith("/account_data/m.cross_signing.self_signing")),
                page.waitForRequest((req) => req.url().endsWith("/account_data/m.cross_signing.user_signing")),
                page.waitForRequest((req) => req.url().endsWith("/account_data/m.megolm_backup.v1")),
                page.waitForRequest((req) => req.url().endsWith("/account_data/m.secret_storage.default_key")),
                page.waitForRequest((req) => req.url().includes("/account_data/m.secret_storage.key.")),
            ];

            await page.getByRole("button", { name: "Delete key storage" }).click();

            await expect(page.getByRole("switch", { name: "Allow key storage" })).not.toBeChecked();

            for (const prom of deleteRequestPromises) {
                const request = await prom;
                expect(request.method()).toBe("PUT");
                expect(request.postData()).toBe(JSON.stringify({}));
            }
        });
    });

    test.describe("when encryption is not set up", () => {
        test("'Verify this device' allows us to become verified", async ({
            page,
            user,
            credentials,
            app,
        }, workerInfo) => {
            const settings = await app.settings.openUserSettings("Encryption");

            // Initially, our device is not verified
            await expect(settings.getByRole("heading", { name: "Device not verified" })).toBeVisible();

            // We will reset our identity
            await settings.getByRole("button", { name: "Verify this device" }).click();
            await page.getByRole("button", { name: "Can't confirm?" }).click();

            // First try cancelling and restarting
            await page.getByRole("button", { name: "Cancel" }).click();
            await page.getByRole("button", { name: "Can't confirm?" }).click();

            // Then click outside the dialog and restart
            await page.locator("li").filter({ hasText: "Encryption" }).click({ force: true });
            await page.getByRole("button", { name: "Can't confirm?" }).click();

            // Finally we actually continue
            await page.getByRole("button", { name: "Continue" }).click();

            // Now we are verified, so we see the Key storage toggle
            await expect(settings.getByRole("heading", { name: "Key storage" })).toBeVisible();
        });
    });
});
